/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package pgentity.minigame.freakingeggs;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import org.apache.commons.pool2.impl.GenericObjectPool;
import share.StringBuilderPoolFactory;

/**
 *
 * @author Salm
 */
public class FEServices {
    private static final int N = 3;
    private static final int N2 = 9;
    private static final GenericObjectPool<StringBuilder> SB_POOL = StringBuilderPoolFactory.makePool();
    
    private final Random rand = new Random(System.currentTimeMillis());
    
    private static final int NP = 3;
    private static final int[][] POOLS = new int[][]
    {
        {0, 1},
        {1, 2},
        {1, 3},
        {1, 4},
        {5, 6}
    };
    
    private static final int[][] POOLS3 = new int[][]
    {
        {0, 1, 2},
        {0, 1, 3},
        {0, 1, 5},
        {4, 5, 6}
    };
    
    private static class GenData
    {
        int d = N2, p = 0;
        int level = 0;
        private final Random rand;

        public GenData(Random rand, int lvl) {
            this.rand = rand;
            this.level = lvl;
        }
        
        void decD()
        {
            d -= 2;
            if (d < 1)
            {
                d = 1;
            }
        }
        
        void incD(int dd)
        {
            d += 2 * dd;
            if (d > N2)
            {
                d = N2;
            }
        }
        
        int shakeD()
        {
            int dd = 1 + rand.nextInt(2);
            if (d < dd)
            {
                return d + 2 * dd;
            }
            else if (d + dd > N2)
            {
                return d - 2 * dd;
            }
            else
            {
                return d + 2 * ((rand.nextDouble() < 0.5)?dd:-dd);
            }
        }
        
        void changeP()
        {
            int newP = rand.nextInt(NP - 1);
            p = (newP >= p)?(newP + 1):newP;
            
            incD(1);
        }
        
        void incLevel()
        {
            ++level;
        }
    }
    
    private static class GenData3
    {
        public static final int _N3 = 3;
        private final Random rand;
        private final int[] data = new int[_N3];

        public GenData3(Random rand) {
            this.rand = rand;
        }
        
        public void gen()
        {
            int d = N2;
            for (int i = 0; i < _N3 - 1; i++) {
                data[i] = rand.nextInt(d);
                d -= data[i];
            }
            
            data[_N3 - 1] = d;
        }
        
        public void fill(int[][] m, int[] pool)
        {
            int r = 0, q = 0;

            for (int i = 0; i < N; ++i)
            {
                for (int j = 0; j < N; ++j)
                {
                    m[i][j] = pool[q];
                    if (++r > data[q])
                    {
                        r = 0;
                        ++q;
                    }
                }
            }
        }
        
        public boolean isEqual(GenData3 gen3)
        {
            for (int i = 0; i < _N3; ++i)
            {
                if (data[i] != gen3.data[i])
                    return false;
            }
            
            return true;
        }
    }
    
    public static class Match
    {
        private final String data;
        private final boolean equal;

        Match(String data, boolean equal) {
            this.data = data;
            this.equal = equal;
        }

        public String getData() {
            return data;
        }

        public boolean isEqual() {
            return equal;
        }
    }
    
    private FEServices()
    {
        
    }
    
    private static final FEServices inst = new FEServices();
    
    public static FEServices inst()
    {
        return inst;
    }
    
    private static final int MAX_LOW = 90;
    public List<Match> generate(int start, int size) throws Exception
    {
        if (size <= 0)
            return Collections.EMPTY_LIST;
        
        int end = start + size;
        if (end <= MAX_LOW)
        {
            return generateLow(start, end);
        }
        
        if (start >= MAX_LOW)
        {
            return generateHigh(size);
        }
        
        List<Match> matches = new ArrayList(size);
        matches.addAll(generateLow(start, MAX_LOW));
        matches.addAll(generateHigh(end - MAX_LOW));
        return matches;
    }
    
    private List<Match> generateLow(int from, int to) throws Exception
    {
        tryUpdateSeed();
        StringBuilder sb = null;
        int size = to - from;
        List<Match> matches = new ArrayList(size);
        try
        {
            sb = SB_POOL.borrowObject();
            
            int[][] match = new int[N][N];
            GenData gen = new GenData(rand, from);
            for (int i = 0; i < size; ++i)
            {
                sb.setLength(0);
                sb.ensureCapacity(0x100);
                nextData(gen);
                
                boolean mode = randBool();
                
                // generate first board
                fill2(match, POOLS[gen.p], gen.d, mode);
                shuffle(match);
                buildString(sb, match);
                
                // second board
                boolean equal = randBool();
                if (!equal)
                {
                    int shakedD = gen.shakeD();
                    fill2(match, POOLS[gen.p], shakedD, mode);
                }
                shuffle(match);
                buildString(sb, match);
                
                sb.append(equal?1:0);
                
                matches.add(new Match(sb.toString(), equal));
            }
        }
        finally
        {
            if (sb != null)
            {
                SB_POOL.returnObject(sb);
            }
        }
        
        return matches;
    }
    
    private List<Match> generateHigh(int size) throws Exception
    {
        tryUpdateSeed();
        StringBuilder sb = null;
        List<Match> matches = new ArrayList(size);
        try
        {
            sb = SB_POOL.borrowObject();
            
            int[][] match = new int[N][N];
            GenData3 gen1 = new GenData3(rand), gen2 = new GenData3(rand);
            int[] pool = POOLS3[rand.nextInt(POOLS3.length)];
            for (int i = 0; i < size; ++i)
            {
                if (randBool(0.3))
                {
                    pool = POOLS3[rand.nextInt(POOLS3.length)];
                }
                
                sb.setLength(0);
                sb.ensureCapacity(0x100);
                gen1.gen();
                gen1.fill(match, pool);
                shuffle(match);
                buildString(sb, match);
                
                // second board
                boolean equal = randBool();
                if (!equal)
                {
                    gen2.gen();
                    equal = gen2.isEqual(gen1);
                    if (!equal)
                    {
                        gen2.fill(match, pool);
                    }
                }
                shuffle(match);
                buildString(sb, match);
                
                sb.append(equal?1:0);
                
                matches.add(new Match(sb.toString(), equal));
            }
        }
        finally
        {
            if (sb != null)
            {
                SB_POOL.returnObject(sb);
            }
        }
        
        return matches;
    }
    
    private void buildString(StringBuilder sb, int[][] m)
    {
        for (int i = 0; i < N; ++i)
        {
            for (int j = 0; j < N; ++j)
            {
                sb.append(m[i][j]).append(";");
            }
        }
    }
    
    private void fill2(int[][] m, int[] pool, int d, boolean mode)
    {
        int p = Math.round((float) (N2 + d) / 2.f);
        p = mode?N2 - p:p;
        int r = 0, q = 0;
        
        for (int i = 0; i < N; ++i)
        {
            for (int j = 0; j < N; ++j)
            {
                if (++r > p)
                {
                    r = 0;
                    p = N2 - p;
                    ++q;
                }
                m[i][j] = pool[q];
            }
        }
    }
    
    private void shuffle(int[][] m)
    {
        for (int i = 0; i < N2; ++i)
        {
            for (int j = i + 1; j < N2; ++j)
            {
                boolean shuffle = randBool();
                if (shuffle)
                {
                    int r1 = i / N, c1 = i % N;
                    int r2 = j / N, c2 = j % N;
                    
                    int k = m[r1][c1];
                    m[r1][c1] = m[r2][c2];
                    m[r2][c2] = k;
                }
            }
        }
    }
    
    private void tryUpdateSeed()
    {
        boolean needUpdate = rand.nextDouble() < 0.2;
        if (needUpdate)
        {
            rand.setSeed(System.currentTimeMillis());
        }
    }
    
    private boolean randBool()
    {
        return rand.nextDouble() < 0.5;
    }
    
    private boolean randBool(double trueRate)
    {
        return rand.nextDouble() < trueRate;
    }
    
    private static final double CHPRATE = -4;
    private static final double CHPMAX = 0.5;
    private static final double DECD = 0.995;
    private static final double DECDRATE = DECD/N2;
    private void nextData(GenData data)
    {
        double chp = CHPRATE / (data.level + 1) + CHPMAX;
        double chpRate = -chp * data.d * data.d / (N2 * N2) + chp;
        double decDRate = DECDRATE * data.d;
        double r = rand.nextDouble();
        if (r < chpRate)
        {
            data.changeP();
        }
        else if (r < chpRate + decDRate)
        {
            data.decD();
        }
        
        data.incLevel();
    }
}
